package x.ovo.wechat.bot.core.plugin;

import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import x.ovo.wechat.bot.core.Constant;
import x.ovo.wechat.bot.core.Context;
import x.ovo.wechat.bot.core.command.CommandExcutor;
import x.ovo.wechat.bot.core.event.EventListener;
import x.ovo.wechat.bot.core.exception.PluginException;
import x.ovo.wechat.bot.core.util.YmlUtil;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 插件基类
 *
 * @author ovo, created by 2024/07/06
 */
@Data
@Slf4j(topic = "Plugin")
public abstract class Plugin {
    /** 插件的优先级，默认为10 */
    private int priority = 10;
    /** 插件是否启用，默认为true */
    private boolean enabled = true;

    /** 上下文环境 */
    protected Context context;
    /** 插件的配置信息 */
    protected volatile Map<String, ?> config;
    /** 类加载器，用于加载插件类 */
    private ClassLoader classLoader;
    /** 插件描述信息，包含插件的基本信息 */
    protected PluginDescription pluginDesc;

    /**
     * 插件加载时调用的方法，用于执行插件的初始化操作。
     */
    public void onLoad() {
    }

    /**
     * 插件卸载时调用的方法，常用于释放资源。
     */
    public void onUnload() {
    }

    /**
     * 获取插件的事件监听器。
     *
     * @return 事件监听器对象。
     */
    public abstract EventListener<?, ?> getEventListener();

    /**
     * 获取插件的命令执行器。
     *
     * @return 命令执行器对象。
     */
    public abstract CommandExcutor getCommandExcutor();

    /**
     * 获取插件的数据目录。
     * 如果目录不存在，则创建该目录。
     *
     * @return 插件数据目录的文件对象。
     */
    public File getDataDir() {
        return new File(Constant.Files.PLUGIN_DIR, this.pluginDesc.getName());
    }

    /**
     * 根据资源名获取资源的输入流。
     *
     * @param name 资源的名称。
     * @return 资源的输入流。
     */
    public InputStream getResource(String name) {
        return classLoader.getResourceAsStream(name);
    }

    /**
     * 重新加载插件的配置文件。
     */
    public void reloadConfig() {
        File file = new File(this.getDataDir(), Constant.Files.CONFIG_FILE_NAME);
        if (file.exists()) {
            this.config = YmlUtil.load(file);
        }
    }

    /**
     * 获取插件的配置信息。
     * 如果配置尚未加载，则先加载配置。
     *
     * @return 插件的配置信息。
     */
    public <V> Map<String, V> getConfig() {
        try {
            if (Objects.nonNull(this.config)) return (Map<String, V>) this.config;
            File file = new File(this.getDataDir(), Constant.Files.CONFIG_FILE_NAME);
            if (file.exists()) {
                this.config = YmlUtil.load(file);
            }
            if (Objects.isNull(this.config)) this.config = new HashMap<>();
            return (Map<String, V>) this.config;
        } catch (Exception e) {
            log.error("插件 [{}] 配置文件加载失败", this.pluginDesc.getName());
            throw new PluginException("插件 [" + this.pluginDesc.getName() + "] 配置文件加载失败", e);
        }
    }

    /**
     * 保存插件的配置信息到文件。
     */
    public void saveConfig() {
        YmlUtil.save(this.config, new File(this.getDataDir(), Constant.Files.CONFIG_FILE_NAME));
    }

    /**
     * 保存插件的默认配置文件。
     * 如果默认配置文件不存在，则从资源中复制到数据目录。
     *
     * @throws PluginException 如果保存失败，则抛出插件异常。
     */
    @SneakyThrows
    public void saveDefaultConfig() {
        this.getDataDir().mkdir();
        try (InputStream inputStream = this.getResource(Constant.Files.CONFIG_FILE_NAME);
             FileOutputStream outputStream = new FileOutputStream(new File(this.getDataDir(), Constant.Files.CONFIG_FILE_NAME));
        ) {
            byte[] bytes = new byte[inputStream.available()];
            if (bytes.length == 0) return;

            int length;
            while ((length = inputStream.read(bytes)) != -1) {
                outputStream.write(bytes, 0, length);
            }
            outputStream.flush();
        } catch (Exception e) {
            log.error("插件 [{}] 默认配置文件保存失败", this.pluginDesc.getName());
            throw new PluginException("插件 [" + this.pluginDesc.getName() + "] 默认配置文件保存失败");
        }
    }
}
